"""
Defines message (python objects, and 'types') used by the Posnet Protocol

all messages objects are a baseclass of the object "posnet_overlay_message"
each posnet message correspond to one object (subclass of the posnet_overlay_message class=) and one integer.
Example:
   the "ping" message in posnet correspond to the 'posnet_ping_message' object (subclass of posnet_overlay_message)
   and its type is the integer 'posnet_message_type_ping'

When a message is sent between two nodes of the overlay,
all attributes of the posnet_overlay_message object are encoded as fields of the binary message,
except the data attribute that is first serialized (the corresponding python object is transformed into a stream) to be encodable as bytes
"""
import array
import cStringIO
import cPickle
import time,array,struct

class posnet_overlay_message(object):
	""" the base class for a message exchanged by nodes in the overlay """
	def __init__(self,source,target,mtype,data = None,atype = None):
		""" constructor"""
		object.__init__(self)
		self.source = source # source address in the overlay
		self.target = target # target address in the overlay
		self.message_type = mtype # overlay message type
		if atype is None: atype = 0
		self.answer_type = atype
		if  data is None: data = ""
		self.data = data # data encapsulated in this message
#-----------------------#
# Message serialization functions
#    the python cPickle module is used
#    data_serializer  : transform some data to cPiclke format
#    data_unserializer : transform cPickle content into data
#-----------------------#
def data_serializer(data):
	buffer  = cStringIO.StringIO()
	try:	
		cPickle.dump(data, buffer, 0)
		return buffer.getvalue()
	finally: buffer.close()

def data_unserializer(mystring):
	""" packing base function"""
	buffer = cStringIO.StringIO(mystring)
	try:
		return cPickle.load(buffer)
	finally: buffer.close()
	
#-----------------------
# Message packing/unpacking functions
#    it is used to transform a string into binary data
#-----------------------#
def array_to_overlay_message_type(bytes_tab):
	iter =  array.array("c", bytes_tab.tostring())
	message_type = int( struct.unpack("H", "%c%c" % ( iter.pop(0),iter.pop(0)))[0] )
	return message_type

def array_to_overlay_message_answer_type(bytes_tab):
	iter =  array.array("c", bytes_tab.tostring())
	iter.reverse()
	iter.pop(-1) #message_type 
	iter.pop(-1) #message_type 
	message_source_len = int(struct.unpack("H", "%c%c" % ( iter.pop(-1),iter.pop(-1)) )[0])
	for i in xrange(message_source_len): iter.pop(-1)
	iter.pop(-1)
	iter.pop(-1)
	# target
	message_target_len = int( struct.unpack("H", "%c%c" %  ( iter.pop(-1),iter.pop(-1)))[0] )
	for i in xrange(message_target_len): iter.pop(-1)
	iter.pop(-1)
	iter.pop(-1)
	answer_type = int( struct.unpack("H", "%c%c" % ( iter.pop(-1),iter.pop(-1)) )[0] )
	return answer_type

def array_to_overlay_message(bytes_tab):
	iter =  array.array("c", bytes_tab.tostring())
	iter.reverse()
	message_type = int( struct.unpack("H", "%c%c" % (iter.pop(-1),iter.pop(-1)))[0] )
	# source
	message_source_len = int( struct.unpack("H", "%c%c" % (  iter.pop(-1),iter.pop(-1)))[0] )
	source_address = ''.join( [ iter.pop(-1) for x in xrange( message_source_len) ] )
	source_port = int(  struct.unpack("H", "%c%c" %(  iter.pop(-1),iter.pop(-1)) )[0] )
	# target
	message_target_len = int( struct.unpack("H", "%c%c" % (iter.pop(-1),iter.pop(-1)))[0] )
	target_address = ''.join( [ iter.pop(-1) for x in xrange( message_target_len) ] )
	target_port = int( struct.unpack("H", "%c%c" % (iter.pop(-1),iter.pop(-1)))[0] )
	answer_type = int( struct.unpack("H", "%c%c" % (iter.pop(-1),iter.pop(-1)))[0] )
	iter.reverse()
	#----------------
	return posnet_overlay_message((source_address,source_port),(target_address,target_port),message_type,data_unserializer(iter.tostring()),answer_type)


def overlay_message_to_array(message):
	buf = array.array("c")
	buf.fromstring(struct.pack("H",int(message.message_type)))
	#--- source
	source_address,source_port  =  message.source
	buf.fromstring(struct.pack("H",int( len(source_address ))))
	buf.fromstring(source_address)
	buf.fromstring(struct.pack("H",int( source_port )))
	#--- target 
	target_address,target_port  =  message.target
	buf.fromstring(struct.pack("H",int( len(target_address ))))
	buf.fromstring(target_address)
	buf.fromstring(struct.pack("H",int( target_port )))
	
	buf.fromstring(struct.pack("H",int( message.answer_type )))
	buf.fromstring(data_serializer(message.data))
	return buf	

#-------------------------------#
# posnet overlay message types  #
#
#  + open/close overlay link : open,link_opened,close
#  + query: query, result iteration
#  + lock: lock,unlock,lock_reply
#
#-------------------------------#

#-------------------------------
# open/close overlay link
posnet_message_type_open_link = 0 # request the opening of an overlay link
posnet_message_type_link_opened = 1 # reply sent to and opening message
posnet_message_type_close_link = 2 # closing a link

class posnet_open_link_message(posnet_overlay_message):
	def __init__(self,source,target,space_point):
		posnet_overlay_message.__init__(self,source,target,posnet_message_type_open_link,space_point)
class posnet_link_opened_message(posnet_overlay_message):
	def __init__(self,source,target,space_point):
		posnet_overlay_message.__init__(self,source,target,posnet_message_type_link_opened,space_point)
class posnet_close_link_message(posnet_overlay_message):
	def __init__(self,source,target,space_point):
		posnet_overlay_message.__init__(self,source,target,posnet_message_type_close_link,space_point)

#-------------------------------
# query
posnet_message_type_query = 3 # query 
posnet_message_type_result = 4 # query 
posnet_message_type_iteration = 5 # query 

class posnet_query_message(posnet_overlay_message):
	def __init__(self,source,target,query_message): posnet_overlay_message.__init__(self,source,target,posnet_message_type_query,query_message)
class posnet_result_message(posnet_overlay_message):
	def __init__(self,source,target,result_message): posnet_overlay_message.__init__(self,source,target,posnet_message_type_result,result_message)
class posnet_iteration_message(posnet_overlay_message):
	def __init__(self,source,target,iteration_message): posnet_overlay_message.__init__(self,source,target,posnet_message_type_iteration,iteration_message)

#-------------------------------
# lock/unlock  [ TODO? change way that lock works ]
posnet_message_type_lock = 6 # lock a node
posnet_message_type_unlock = 7 # unclock
posnet_message_type_lock_reply = 8 # confirmation of lock

class posnet_lock_message(posnet_overlay_message):
	def __init__(self,source,target,lock_message = None): posnet_overlay_message.__init__(self,source,target,posnet_message_type_lock,lock_message)
class posnet_unlock_message(posnet_overlay_message):
	def __init__(self,source,target,unlock_message = None): posnet_overlay_message.__init__(self,source,target,posnet_message_type_unlock,unlock_message)
class posnet_lock_reply_message(posnet_overlay_message):
	def __init__(self,source,target,lock_reply_message = None): posnet_overlay_message.__init__(self,source,target,posnet_message_type_lock_reply,lock_reply_message)

#-------------------------------
# user request to store data or node join
# join
posnet_message_type_join = 9 # a node wants to join the posnet network
posnet_message_type_store = 10 # store a data

class posnet_join_message(posnet_overlay_message):
	def __init__(self,source,target,who_wants_to_join):
		posnet_overlay_message.__init__(self,source,target,posnet_message_type_join,who_wants_to_join)
class posnet_store_message(posnet_overlay_message):
	def __init__(self,source,target,store_message):
		posnet_overlay_message.__init__(self,source,target,posnet_message_type_store,store_message)

#-------------------------------
# request for mod :)
# local updates of a node
posnet_message_type_add_handle = 11 
posnet_message_type_update_topology = 12
posnet_message_type_split = 13
class posnet_add_handle_message(posnet_overlay_message):
	def __init__(self,source,target,rh_message):
		posnet_overlay_message.__init__(self,source,target,posnet_message_type_add_handle,rh_message)
class posnet_update_topology_message(posnet_overlay_message):
	def __init__(self,source,target,update_message):
		posnet_overlay_message.__init__(self,source,target,posnet_message_type_update_topology,update_message)
class posnet_split_message(posnet_overlay_message):
	def __init__(self,source,target,split_message = None):
		posnet_overlay_message.__init__(self,source,target,posnet_message_type_split,split_message)

posnet_message_type_ping = 14
posnet_message_type_ping_reply = 15
class posnet_ping_message(posnet_overlay_message):
	def __init__(self,source,target,message = None,answer_type = None):
		posnet_overlay_message.__init__(self,source,target,posnet_message_type_ping,message,answer_type)
class posnet_ping_reply_message(posnet_overlay_message):
	def __init__(self,source,target,message = None):
		 posnet_overlay_message.__init__(self,source,target,posnet_message_type_ping_reply,message)

posnet_message_type_overlay_add_handle = 16
class posnet_overlay_add_handle_message(posnet_overlay_message):
	def __init__(self,source,target,rh_message):
		posnet_overlay_message.__init__(self,source,target,posnet_message_type_overlay_add_handle,rh_message)

posnet_message_type_draw = 17
posnet_message_type_picture = 18
posnet_message_type_graph_request = 19
posnet_message_type_graph_result = 20

class posnet_draw_message(posnet_overlay_message):
	def __init__(self,source,target,draw_options):
		posnet_overlay_message.__init__(self,source,target,posnet_message_type_draw,draw_options)
class posnet_picture_message(posnet_overlay_message):
	def __init__(self,source,target,picture):
		posnet_overlay_message.__init__(self,source,target,posnet_message_type_picture,picture)
class posnet_graph_request_message(posnet_overlay_message):
	def __init__(self,source,target,graph_request_options):
		posnet_overlay_message.__init__(self,source,target,posnet_message_type_graph_request,graph_request_options)
class posnet_graph_result_message(posnet_overlay_message):
	def __init__(self,source,target,graph_result):
		posnet_overlay_message.__init__(self,source,target,posnet_message_type_graph_result,graph_result)

posnet_message_type_finish_send_results = 21

class posnet_finish_send_results(posnet_overlay_message):
   def __init__(self,source,target,query_id):
      posnet_overlay_message.__init__(self,source,target,posnet_message_type_finish_send_results,query_id)


posnet_message_type_state_request = 22
posnet_message_type_state_result = 23

class posnet_state_request_message(posnet_overlay_message):
	def __init__(self,source,target,state_request_options):
		posnet_overlay_message.__init__(self,source,target,posnet_message_type_state_request,state_request_options)
class posnet_state_result_message(posnet_overlay_message):
	def __init__(self,source,target,state_result):
		posnet_overlay_message.__init__(self,source,target,posnet_message_type_state_result,state_result)


#------------------------------------------------------------
#posnet_message_classes = [posnet_open_link_message, posnet_link_opened_message, posnet_close_link_message, \
#			  posnet_query_message, posnet_result_message, posnet_iteration_message, \
#			  posnet_lock_message, posnet_unlock_message, posnet_lock_reply_message, \
#			  posnet_join_message, posnet_store_message, \
#			  posnet_add_handle_message, posnet_update_topology_message, posnet_split_message, \
#			  posnet_message_type_ping ]
#				
#------------------------------------------------------------


# posnet query
class posnet_semantic_query(object):
	""" this class represent a query (that is encapsulated within a query_message as the data attribute)  """
	def __init__(self,requester,target,qtype,data,source = None):
		""" constructor"""
		object.__init__(self)
		self.id = None               # Unique ID of this query!
		self.requester = requester   # query initiator
		self.source = source         # space_point that is current source of this query 
		self.target = target         # space_point that is current target of this query
		self.query_type = qtype      # the query type
		self.query = data            # data in this object (depends on query type)
		self.iteration_mode = False  # if True, set iteration mode
		self.record_state = False    # if True, it will record states of results

	def set_id(self,id_param):
		self.id = id_param
	def generate_id(self):
		self.id = self.__hash__
	
	def copy(self):
		o = posnet_semantic_query(self.requester,self.target,self.query_type,self.query,self.source)
		o.id = self.id
		o.iteration_mode = self.iteration_mode
		o.record_state = self.record_state
		return o

	def result(self):
		""" build and return a query_result object with the result specified"""
		return posnet_semantic_query_result(self.query_type,self.query,self.id)

	def __str__(self):
		return " ".join([str(item) for item in [self.requester,"---->",self.source,"->",self.target,"<",self.query_type,">",self.query]])

# posnet query
class posnet_semantic_multiquery(object):
	""" this class represent a query (that is encapsulated within a query_message as the data attribute)  """
	def __init__(self,requester,target,qtype,data,source = None):
		""" constructor"""
		object.__init__(self)
		self.id = None               # Unique ID of this query!
		self.requester = requester   # query initiator
		self.source_and_targets = []
		#source         # space_point that is current source of this query 
		#target         # space_point that is current target of this query
		if target is not None: self.source_and_targets.append( (source,target) )
		self.query_type = qtype      # the query type
		self.query = data            # data in this object (depends on query type)
		self.iteration_mode = False  # if True, set iteration mode
		self.record_state = False    # if True, it will record states of results

	def add(self,target,source = None):
		if target is not None: self.source_and_targets.append( (source,target) )

	def set_id(self,id_param):
		self.id = id_param
	def generate_id(self):
		self.id = self.__hash__
	
	def copy(self):
		o = posnet_semantic_multiquery(self.requester,None,self.query_type,self.query)
		#o.source_and_targets = self.source_and_targets
		o.source_and_targets = []
		o.id = self.id
		o.iteration_mode = self.iteration_mode
		o.record_state = self.record_state
		return o

	def source_and_targets_str(self,join_char = None):
	  if join_char is None: join_char = ""
	  return join_char.join( ["{"+str(sourcee)+" -> "+str(targett)+"}" for sourcee,targett in  self.source_and_targets])	  

	def result(self):
		""" build and return a query_result object with the result specified"""
		return posnet_semantic_query_result(self.query_type,self.query,self.id)

	def __str__(self):
		return " ".join([str(item) for item in [self.requester,"---->",self.source_and_targets_str(""),"<",self.query_type,">",self.query]])


class posnet_semantic_query_result(object):
	""" this class is used to transport one result with corresponding query """ 
	def __init__(self,qtype,query,query_id):
		""" constructor """
		object.__init__(self)
		self.query_type = qtype      # the query type
		self.query = query           # data in this object (depends on query type)
		self.query_id = query_id     # query id
		self.results = []         # result of the query
		self.states = []           # state of the result
		self.next = []               # next nodes to contact
		#self.iterative = False

	def add_next(self,next_node):
		self.next.append(next_node)
	def add_result(self,result,state= None):
		self.results.append(result)
		self.states.append(state)

	def __str__(self):
		return "query of "+str(self.query)+" -> results="+str(self.results)+", next="+str(self.next)+", states="+str(self.states)



class posnet_add_handle(object):
	def __init__(self,space_point,handle,wait = None):
		object.__init__(self)
		self.space_point = space_point
		self.handle = handle
		if wait is None: wait = False
		self.wait = wait

class posnet_update_topology(object):
	def __init__(self):
		object.__init__(self)
		self.add = []
		self.remove = []
		self.ext = []
	def add_link_addition(self,space_point_source,space_point_target,relation,relation_sym):
		self.add.append( (space_point_source,space_point_target,relation,relation_sym) )	
	def add_link_removal(self,space_point_source,space_point_target):
		self.remove.append( (space_point_source,space_point_target) )	
	def add_external_link_addition(self,overlay_node, space_point_source,space_point_target,relation,relation_sym):
		self.ext.append( (overlay_node,space_point_source,space_point_target,relation,relation_sym) )

class posnet_query_iteration(object):
	def __init__(self,qtype,query,results,propagations,states = None):
		""" constructor"""
		object.__init__(self)
		self.query_type = qtype # the query type
		self.query = query
		self.results = results # table of results
		self.propagations = propagations # table of (source,target,neighbor)
		if states is None: states = []
		self.states = states # table of  states for results
		self.success = True

	def set_success(self,val):
		if val is None or val:  self.success = True
		else: self.success = False

#---------------------------------------------------------
# objects used in lock, lock_reply and unlock messages
#---------------------------------------------------------

class posnet_lock(object):
	def __init__(self,space_points_and_states_dict):
		""" constructor"""
		object.__init__(self)
		self.space_points_and_states = space_points_and_states_dict 

class posnet_lock_reply(object):
	def __init__(self,space_points_and_states_dict,okay):
		object.__init__(self)
		self.space_points_and_states = space_points_and_states_dict
		self.success = okay
	def __str__(self):	return str(self.space_points_and_states)+" states="+str(self.success)
class posnet_unlock(object):
   def __init__(self,space_points_tab,is_cancel_param):
      object.__init__(self)
      self.space_points = space_points_tab
      self.is_cancel = is_cancel_param
